package graphics.system;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.LinkedList;

import javax.imageio.ImageIO;

import main.Config;

import org.lwjgl.BufferUtils;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.GL11;

import sound.main.SoundManager;
import tools.Floatmath;
import tools.Log;
import tools.Point;
import tools.Profiler;

import graphics.effects.Compositor;
import graphics.effects.Layer;
import graphics.effects.PartSys;
import graphics.effects.GEffect;
import graphics.main.MainRenderer;
import graphics.objects.GCircle;
import graphics.objects.GLight;
import graphics.objects.GPolygon;
import graphics.objects.GRect;
import graphics.objects.GShape;
import graphics.objects.GraphicsObject;
import graphics.synchronize.GSyncTools;
import graphics.tools.Console;
public abstract class Renderer {
	protected transient LinkedList<GraphicsObject> objects;
	protected transient ArrayList<GLight> lights;
	protected float width;
	protected float height;
	protected Point campos;
	protected float zoom;
	public Renderer() {
		objects = new LinkedList<GraphicsObject>();
		lights = new ArrayList<GLight>();
	}
	/**
	 * Renders something. You have to specify how all objects will be rendered
	 */
	public abstract void render();
	/**
	 * Initializes the renderer (Framebuffers, Shaders, etc. )
	 * 
	 * By default everything for standard 2D-drawing is set (ShadeModel = GL_SMOOTH, Depth and Lighting is disabled,...)
	 * 
	 */
	public void initializeGraphicsSystem() {
		GL11.glDisable(GL11.GL_DEPTH_TEST);
		GL11.glShadeModel(GL11.GL_SMOOTH);
		GL11.glDisable(GL11.GL_LIGHTING);
		GL11.glEnable(GL11.GL_BLEND);
		GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
		GL11.glEnable(GL11.GL_LINE_SMOOTH);
		GL11.glEnable(GL11.GL_COLOR_MATERIAL);
		width = Config.RESOLUTION.x;
		height = Config.RESOLUTION.y;
		this.setCameraPosition(new Point(0, 0));
		this.zoom = 1;
		TextureContainer.loadTextures();
		PartSys.init();
		Layer.init();
		GSyncTools.init();
		Compositor.init();
		GLTools.init();
	}
	public void addObject(GraphicsObject obj) {
		if (obj.islight) {
			lights.add((GLight) obj);
		}
		// insertion sort
		int i = 0;
		while (i < objects.size() && objects.get(i).node.zpos < obj.node.zpos)
			i++;
		objects.add(i, obj);
	}
	protected void zOrder() {
		Profiler.startZOrder();
		for (int i = 0; i < objects.size() - 1; i++) {
			for (int j = i + 1; j < objects.size(); j++) {
				if (objects.get(i).node.zpos > objects.get(j).node.zpos) {
					GraphicsObject gro = objects.get(i); // swap buffer
					objects.set(i, objects.get(j));
					objects.set(j, gro);
				}
			}
		}
		Profiler.stopZOrder();
	}
	public void removeObject(GraphicsObject obj) {
		objects.remove(obj);
		if (obj instanceof GLight) {
			lights.remove(obj);
		}
	}
	public void renderAllColor() {
		for (GraphicsObject go : objects) {
			if (go.node.visible && go.color.a > 0)
				go.draw();
		}
	}
	public void renderAllShadow(Point lightPos, float radius) {
		float rsq = radius * radius;
		for (GraphicsObject go : objects) {
			if (go instanceof GShape && ((GShape) go).hasShadow() && (go.node.pos.squaredDistance(lightPos) - (go.node.boundingrad * go.node.boundingrad)) < rsq) {
				go.drawShadow(lightPos, radius);
			}
		}
	}
	public void renderAllSpecular(Point lightPos, float radius, Color lc) {
		float rsq = radius * radius;
		if (lc.a > 0) {
			for (GraphicsObject go : objects) {
				if (go instanceof GShape && (go.node.pos.squaredDistance(lightPos) - (go.node.boundingrad * go.node.boundingrad)) < rsq) {
					go.drawSpec(lightPos, radius, lc, zoom);
				}
			}
		}
	}
	public void renderAllWarp() {
		for (GraphicsObject go : objects) {
			if (go.node.visible)
				go.drawWarp();
		}
	}
	public void renderAllGlow() {
		for (GraphicsObject go : objects) {
			if (go.node.visible && !(go instanceof GLight))
				go.drawGlow();
		}
	}
	protected void renderAllNodes() {
		for (GraphicsObject go : objects) {
			go.node.debugDraw();
		}
	}
	protected void renderAllDebug() {
		for (GraphicsObject go : objects) {
			go.drawDebug();
		}
	}
	protected void updateVisibility() {
		float screens = (1.0f / zoom) * (Floatmath.max(width, height));
		for (GraphicsObject go : objects) {
			if (campos.distance(go.node.pos) - go.node.boundingrad > screens) {
				go.node.visible = false;
			}
			else {
				go.node.visible = true;
			}
		}
	}
	public void checkEffects() {
		for (int i = 0; i < objects.size(); i++) {
			if (objects.get(i).iseffect) {
				if (!((GEffect) objects.get(i)).isAlive()) {
					if (((GEffect) objects.get(i)).islight) {
						lights.remove(objects.get(i));
					}
					objects.remove(i);
					i--;
				}
			}
		}
	}
	public void takeScreenshot() {
		GL11.glReadBuffer(GL11.GL_FRONT);
		ByteBuffer buffer = BufferUtils.createByteBuffer((int) width * (int) height * 4);
		GL11.glReadPixels(0, 0, (int) width, (int) height, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer);
		Calendar c = Calendar.getInstance();
		String date = c.get(Calendar.YEAR) + "_" + (c.get(Calendar.MONTH) + 1) + "_" + c.get(Calendar.DAY_OF_MONTH) + "_" + (c.get(Calendar.HOUR) + (c.get(Calendar.AM_PM) == Calendar.AM ? 0 : 12)) + "_" + c.get(Calendar.MINUTE) + "_" + c.get(Calendar.SECOND);
		File file = new File(Config.SCREENSHOTFOLDER + "shot_" + date + ".png");
		String format = "PNG";
		BufferedImage image = new BufferedImage((int) width, (int) height, BufferedImage.TYPE_INT_RGB);
		for (int x = 0; x < (int) width; x++)
			for (int y = 0; y < (int) height; y++) {
				int i = (x + ((int) width * y)) * 4;
				int r = buffer.get(i) & 0xFF;
				int g = buffer.get(i + 1) & 0xFF;
				int b = buffer.get(i + 2) & 0xFF;
				image.setRGB(x, (int) height - (y + 1), (0xFF << 24) | (r << 16) | (g << 8) | b);
			}
		try {
			ImageIO.write(image, format, file);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public void setCameraPosition(Point center) {
		this.campos = new Point(center);
	}
	public void setZoom(float zoom) {
		this.zoom = Math.max(0, Math.min(zoom, 10));
	}
	public void startCamTransformationQualityScale(Framebuffer fb) {
		GL11.glPushMatrix();
		float scx = fb.xres / width;
		float scy = fb.yres / height;
		GL11.glScalef(scx,scy, 1);
		GL11.glScalef(zoom, zoom, 1);
		GL11.glTranslatef(-campos.x + ((width / 2.0f) * (1.0f / zoom)), -campos.y + ((height / 2.0f) * (1.0f / zoom)), 0);
	}
	public void startCamTransformation() {
		GL11.glPushMatrix();
		GL11.glScalef(zoom, zoom, 1);
		GL11.glTranslatef(-campos.x + ((width / 2.0f) * (1.0f / zoom)), -campos.y + ((height / 2.0f) * (1.0f / zoom)), 0);
	}
	public void stopCamTransformation() {
		GL11.glPopMatrix();
	}
	public Point getMousePosTransformed() {
		Point mp = new Point(Mouse.getX(), Mouse.getY());
		mp = campos.sub(new Point(width / 2 / zoom, height / 2 / zoom)).add(mp.mul(1 / zoom));
		return mp;
	}
	public Point getMousePosOnScreen() {
		Point mp = new Point(Mouse.getX(), Mouse.getY());
		return mp;
	}
	protected void handleGraphicLogic() {
		checkEffects();
		PartSys.handle();
	}
	public void handle() {
	}
	public int getWantedFps() {
		return Config.MAXFPS;
	}
	public void setCurrentFps(int fps) {
	}
	public void resize(int width, int height) {
		this.width = width;
		this.height = height;
		Console.width = width;
		Console.height = height;
		Layer.resize(width, height);
	}
}
